Passed
Push — master ( 0bf002...1f0e76 )
by Kolja
01:11
created

jgfGraph.js ➔ _findNode   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
const check = require('check-types');
2
const _ = require('lodash');
3
const { cloneObject } = require('./common');
4
const { JGFEdge } = require('./jgfEdge');
5
const { Guard } = require('./guard');
6
7
/**
8
 * A single JGF graph instance, always contained in a parent JGFContainer
9
 */
10
class JGFGraph {
11
12
    /**
13
     * Constructor
14
     * @param {*} type graph classification
15
     * @param {*} label a text display for the graph
16
     * @param {*} directed true for a directed graph, false for an undirected graph
17
     * @param {*} metadata about the graph
18
     */
19
    constructor(type = '', label = '', directed = true, metadata = null) {
20
        this._nodes = [];
21
        this._edges = [];
22
23
        this._type = type;
24
        this._label = label;
25
        this._directed = directed;
26
        this._metadata = metadata;
27
    }
28
29
30
    /**
31
     * Loads the graph from a JGF JSON object
32
     * @param {*} graphJson JGF JSON object
33
     */
34
    loadFromJSON(graphJson) {
35
        this._type = graphJson.type;
36
        this._label = graphJson.label;
37
        // todo: this makes the graph always directed (even if false is passed), I doubt that this was the intention here
38
        this._directed = graphJson.directed || true;
39
        this._metadata = graphJson.metadata;
40
41
        this._nodes = [];
42
        this._edges = [];
43
        this.addNodes(graphJson.nodes);
44
        this.addEdges(graphJson.edges);
45
    }
46
47
    /**
48
     * @param {string} nodeId Node to be found.
49
     * @private
50
     */
51
    _findNodeById(nodeId) {
52
        let foundNode = _.find(this._nodes, (existingNode) => existingNode.id === nodeId);
53
        if (!foundNode) {
54
            throw new Error(`A node does not exist with id = ${nodeId}`);
55
        }
56
57
        return foundNode;
58
    }
59
60
    /**
61
     * @param {JGFNode} node Node to be found.
62
     * @private
63
     */
64
    _nodeExists(node) {
65
        return this._nodeExistsById(node.id);
66
    }
67
68
    /**
69
     * @param {string} nodeId Node to be found.
70
     * @private
71
     */
72
    _nodeExistsById(nodeId) {
73
        let foundNode = _.find(this._nodes, (existingNode) => existingNode.id === nodeId);
74
75
        return Boolean(foundNode);
76
    }
77
78
    /**
79
     * Set the graph meta data
80
     */
81
    set metadata(value) {
82
        Guard.assertValidMetadataOrNull(value);
83
        this._metadata = value;
84
    }
85
86
    /**
87
     * Returns the graph meta data
88
     */
89
    get metadata() {
90
        return this._metadata;
91
    }
92
93
    /**
94
     * Returns all nodes
95
     */
96
    get nodes() {
97
        return this._nodes;
98
    }
99
100
    /**
101
     * Returns all edges
102
     */
103
    get edges() {
104
        return this._edges;
105
    }
106
107
    /**
108
     * Returns the graph as JGF Json
109
     */
110
    get json() {
111
        let json = {
112
            type: this._type,
113
            label: this._label,
114
            directed: this._directed,
115
            nodes: this._nodes,
116
            edges: this.edges,
117
        };
118
119
        let metadata = this._getJsonMetadata();
120
        if (metadata) {
121
            json.metadata = metadata;
122
        }
123
124
        return cloneObject(json);
125
    }
126
127
    _getJsonMetadata() {
128
        let metadata = null;
129
        if (check.assigned(this._metadata)) {
130
            metadata = this._metadata;
131
        }
132
133
        return metadata;
134
    }
135
136
    /**
137
     * Adds a new node
138
     * @param {JGFNode} node Node to be added.
139
     */
140
    addNode(node) {
141
        if (this._nodeExists(node)) {
142
            throw new Error(`A node already exists with id = ${node.id}`);
143
        }
144
145
        this._nodes.push(node);
146
    }
147
148
149
    /**
150
     * Adds multiple nodes
151
     * @param {JGFNode[]} nodes A collection of JGF node objects.
152
     */
153
    addNodes(nodes) {
154
        for (let node of nodes) {
155
            this.addNode(node);
156
        }
157
    }
158
159
    /**
160
     * Removes an existing graph node
161
     * @param {JGFNode} node Node to be removed.
162
     */
163
    removeNode(node) {
164
        if (!this._nodeExists(node)) {
165
            throw new Error(`A node does not exist with id = ${node.id}`);
166
        }
167
168
        _.remove(this._nodes, (existingNode) => existingNode.id === node.id);
169
    }
170
171
    /**
172
     * Get a node by a node id.
173
     * @param {string} nodeId Unique node id
174
     */
175
    getNodeById(nodeId) {
176
        return this._findNodeById(nodeId);
177
    }
178
179
    /**
180
     * Adds an edge between a source node and a target node.
181
     * @param {JGFEdge} edge Source node id
182
     */
183
    addEdge(edge) {
184
        this._guardAgainstNonExistentNodes(edge.source, edge.target);
185
        this._edges.push(edge);
186
    }
187
188
    _guardAgainstNonExistentNodes(source, target) {
189
        if (!this._nodeExistsById(source)) {
190
            throw new Error(`addEdge failed: source node isn't found in nodes. source = ${source}`);
191
        }
192
193
        if (!this._nodeExistsById(target)) {
194
            throw new Error(`addEdge failed: target node isn't found in nodes. target = ${target}`);
195
        }
196
    }
197
198
    /**
199
     * Adds multiple edges
200
     * @param {JGFEdge[]} edges A collection of JGF edge objects.
201
     */
202
    addEdges(edges) {
203
        for (let edge of edges) {
204
            this.addEdge(edge);
205
        }
206
    }
207
208
    /**
209
     * Removes existing graph edge.
210
     * @param {JGFEdge} edge Edge to be removed.
211
     */
212
    removeEdge(edge) {
213
        _.remove(this._edges, (existingEdge) => existingEdge.isEqualTo(edge, true));
214
    }
215
216
    /**
217
     * Get edges between source node and target node, with an optional edge relation.
218
     * @param {string} source Source node ID.
219
     * @param {string} target Target node ID.
220
     * @param {string,null} relation
221
     */
222
    getEdgesByNodes(source, target, relation = null) {
223
        this._guardAgainstNonExistentNodes(source, target);
224
225
        let edge = new JGFEdge(source, target, relation);
226
227
        return _.filter(this._edges, (existingEdge) => existingEdge.isEqualTo(edge, check.assigned(relation)));
228
    }
229
230
    get graphDimensions() {
231
        return {
232
            nodes: this._nodes.length,
233
            edges: this._edges.length,
234
        };
235
    }
236
}
237
238
module.exports = {
239
    JGFGraph,
240
};